/**
 * Copyright (c) 2009 Mujtaba Hassanpur.
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;

/**
 * @author Mujtaba
 *
 */
public class CollapsingBricksGame extends JFrame
	implements WindowListener, ActionListener, KeyListener, ComponentListener
{	
	private static final long serialVersionUID = 1L;
	private Bucket worldObjects;
	private GameTimer timer;
	private BucketView mapView;
	private QuitCommand quitCommand;
	private Container contentPane;
	private GameState state;
	private GameDifficulty difficulty;
	
	// Used to implement Singleton design pattern.
	private static CollapsingBricksGame theGame;
	
	// used to maintain state
	public static enum GameState {INITIAL, LOGO1, MENU, PLAYING, PAUSED, GAME_OVER};
	public static enum GameDifficulty {NONE, EASY, NORMAL, HARD};
	
	// used for the viewing window
	private double windowTop, windowBottom, windowLeft, windowRight;
	
	public static CollapsingBricksGame getCollapsingBricksGame()
	{
		if(theGame == null)
		{
			theGame = new CollapsingBricksGame();
		}
		
		return theGame;
	}
	
	private CollapsingBricksGame()
	{
		// initialize to pause mode
		state = GameState.INITIAL;
		
		// initially difficulty is none, we need to start a game first
		difficulty = GameDifficulty.NONE;
		
		timer = new GameTimer(this);
		
		// get content pane and initiate window listener
		contentPane = this.getContentPane();
		this.addComponentListener(this);
		this.addWindowListener(this);
		this.addKeyListener(this);
		this.setFocusable(true);
		this.requestFocus();
		
		// set up the map view
		worldObjects = Bucket.getBucket();
		mapView = new BucketView(worldObjects);
		
		// initialize viewing window
		windowLeft = 0;
		windowBottom = 0;
		windowRight = Brick.BRICK_SIZE * Bucket.BUCKET_WIDTH;
		windowTop = Brick.BRICK_SIZE * Bucket.BUCKET_HEIGHT;
		mapView.calculateWorldToND(windowTop, windowBottom, windowLeft, windowRight);
		
		// set up the commands
		quitCommand = new QuitCommand();
		
		// set up menu bar
		initializeGUI();
		initialize();
		
		// set the Observers/Observables
		worldObjects.addObserver(mapView);
		
		//TODO add the map to the frame
		contentPane.add(mapView, BorderLayout.CENTER);
		
		setGameState(GameState.MENU);
		setGameDifficulty(GameDifficulty.EASY);
	}
	
	private void initialize()
	{
		//TODO get the random number generator
		worldObjects.setActivePuzzlePiece(PuzzlePiece.createRandomPuzzlePiece());
	}
	
	private void initializeGUI()
	{
		contentPane.setLayout(new BorderLayout());
		this.setJMenuBar(buildMenuBar());
	}
	
	public double getViewingWindowTop()
	{
		return windowTop;
	}
	
	public double getViewingWindowBottom()
	{
		return windowBottom;
	}
	
	public double getViewingWindowLeft()
	{
		return windowLeft;
	}
	
	public double getViewingWindowRight()
	{
		return windowRight;
	}
	
	/**
	 * Returns a reference to the quit command.
	 * @return Returns a reference to the quit command.
	 */
	public Command getQuitCommand()
	{
		return quitCommand;
	}
	
	/**
	 * Builds the menu bar.
	 * @return A reference to the built menu bar.
	 */
	private JMenuBar buildMenuBar()
	{
		JMenuBar menuBar = new JMenuBar();
		CommandHolderMenuItem holderMenuItem;
		
		// the "File" menu
		JMenu fileMenu = new JMenu("File");
		holderMenuItem = new CommandHolderMenuItem("Exit");
		holderMenuItem.setCommand(quitCommand);
		fileMenu.add(holderMenuItem);
		holderMenuItem.addActionListener(this);
		
		// add the menu item(s) to the menu bar
		menuBar.add(fileMenu);
		return menuBar;
	}
	
	public void updateMovables(int elapsedTime)
	{
		worldObjects.updateMovables(elapsedTime);
	}
	
	public void checkCollisions()
	{
		//TODO implement
		//mapView.repaint();
		
		/*
		if(state == GameState.LOGO1)
		{
			try
			{
				Thread.sleep(5000);
				CollapsingBricksGame.getCollapsingBricksGame().setGameState(CollapsingBricksGame.GameState.PLAYING);
			} catch(InterruptedException e) { }
			
			System.out.println("transition to Menu");
			setGameState(GameState.MENU);
		}
		*/
	}
	
	/**
	 * Starts a new game.
	 */
	public void startNewGame()
	{
		worldObjects.removeAll();
		initialize();
	}
	
	/**
	 * Sets the playing mode.
	 * @param enable If true, switch to playing mode, if false, switch to pause.
	 */
	public void setGameState(GameState newState)
	{
		state = newState;
		if(state == GameState.PAUSED)
		{
			timer.stop();
		}
		else
		{
			timer.start();
		}
	}
	
	/**
	 * Gets the current game state.
	 * @return Returns a GameState enum.
	 */
	public GameState getGameState()
	{
		return state;
	}
	
	public void setGameDifficulty(GameDifficulty newDifficulty)
	{
		difficulty = newDifficulty;
		Scoreboard.getScoreboard().updateSpeedModifier(difficulty);
	}
	
	public GameDifficulty getGameDifficulty()
	{
		return difficulty;
	}
	
	/**
	 * Gets the playing mode.
	 */
	public boolean isPlayingMode()
	{
		return (state == GameState.PLAYING);
	}
	
	/**
	 * Prompts the user to confirm exit.
	 * @param e The WindowEvent object.
	 */
	public void windowClosing(WindowEvent e)
	{
		quitCommand.execute();
	}
	
	public void windowActivated(WindowEvent e) { }
	public void windowClosed(WindowEvent e) { }
	public void windowDeactivated(WindowEvent e) { }
	public void windowDeiconified(WindowEvent e) { }
	public void windowIconified(WindowEvent e) { }
	public void windowOpened(WindowEvent e) { }
	
	public void componentMoved(ComponentEvent e) { }
	public void componentHidden(ComponentEvent e) { }
	public void componentShown(ComponentEvent e) { }
	
	/**
	 * Recalculate the affine transforms.
	 */
	public void componentResized(ComponentEvent e)
	{
		mapView.calculateWorldToND(windowTop, windowBottom, windowLeft, windowRight);
		mapView.calculateNDToScreen();
	}
	
	/**
	 * Returns the height of the window.
	 * @return Returns the height of the window.
	 */
	public int getWindowHeight()
	{
		return this.getHeight();
	}
	
	/**
	 * Returns the width of the window.
	 * @return Returns the width of the window.
	 */
	public int getWindowWidth()
	{
		return this.getWidth();
	}
	
	/**
	 * Performs the action associated with the given event.
	 */
	public void actionPerformed(ActionEvent e)
	{
		if(e.getSource() instanceof CommandHolder)
		{
			CommandHolder obj = (CommandHolder)e.getSource();
			obj.getCommand().execute();
		}
		else
		{
			JOptionPane.showMessageDialog(null, "This feature hasn't been implemented yet.");
		}
		
		// gets the focus for the keyboard
		this.requestFocus();
	}
	
	/**
	 * Handles input from the keyboard.
	 */
	public void keyPressed(KeyEvent e)
	{
		if(state == GameState.PLAYING)
		{
			// commands only available in playing mode
			switch(e.getKeyCode())
			{
				//TODO implement this
				case KeyEvent.VK_LEFT:
					worldObjects.getActivePuzzlePiece().moveLeft();
					worldObjects.notifyChanges();
					break;
					
				case KeyEvent.VK_RIGHT:
					worldObjects.getActivePuzzlePiece().moveRight();
					worldObjects.notifyChanges();
					break;
					
				case KeyEvent.VK_DOWN:
					worldObjects.getActivePuzzlePiece().setState(PuzzlePiece.PieceState.ACCELERATED_FALL);
					worldObjects.notifyChanges();
					break;
					
				case KeyEvent.VK_SPACE:
					worldObjects.getActivePuzzlePiece().cycleOrientation();
					worldObjects.notifyChanges();
					break;
					
				case KeyEvent.VK_ESCAPE:
					setGameState(GameState.PAUSED);
					worldObjects.notifyChanges();
					break;
					
				default:
					break;
			}
		}
		else if(state == GameState.PAUSED)
		{
			// commands only available in pause mode
			switch(e.getKeyCode())
			{
				case KeyEvent.VK_ESCAPE:
					setGameState(GameState.PLAYING);
					worldObjects.notifyChanges();
					break;
					
				default:
					break;
			}
		}
		else if(state == GameState.MENU)
		{
			// currently enter starts the game
			switch(e.getKeyCode())
			{
				case KeyEvent.VK_ENTER:
					setGameDifficulty(GameDifficulty.EASY);
					setGameState(GameState.PLAYING);
					worldObjects.notifyChanges();
					break;
					
				default:
					break;
			}
		}
		
		// commands available anytime
		switch(e.getKeyCode())
		{
			//TODO implement this
			default:
				break;
		}
	}
	
	public void keyReleased(KeyEvent e) { }
	public void keyTyped(KeyEvent e) { }
}
